ML.NET机器学习教程

机器视觉术语表

准确度

真实结果占总案例的比值。从0(最不准确)到1(最准确)。准确度是用来为您的模型性能评分的唯一评估手段,同时您也应该与精确率召回率一同考虑。

曲线下面积

表示当假正 (flase positives) 描绘在 X 轴上,而真正 (true positives) 被描绘到 y 轴上时候的面积。范围从0.5(最差)到1(最好)。

二分类

一个分类情况,标签指向两个类别中的一个。有关更多信息,请参阅Wikipedia上的二分类文章。

分类

当数据被用来预测一个类别时,监督学习也被称为分类。二分类是指仅预测两个类别(例如,将图像分配为“猫”或“狗”的图片)。多分类是指预测多个类别(例如,将图像分类为特定品种的狗时)。

决定系数

一个表示数据如何适合模型的数字。值为1表示模型与数据完全匹配。值为0意味着数据是随机的,或者不适合模型。这通常被称为$r^{2}$,$R^{2}$或r-squared。

特征

被测量现象的可衡量属性,通常为数值(double value)。多个特征称为**特征向量**,通常存储为double[]。特征定义了关于正在测量的现象的重要特征。欲了解更多信息,请参阅维基百科上的[专题](https://en.wikipedia.org/wiki/Feature_(machine_learning))文章。

特征工程

特征工程是开发将其他数据类型(记录,对象,…)转换为特征向量的软件的过程。由此产生的软件执行特征提取。欲了解更多信息,请参阅维基百科上的特性工程文章。

F-得分

衡量测试精度的衡量标准,可平衡精确度召回率

超参数

机器学习算法的参数。示例包括决策树中要学习的树的数量或梯度下降算法中的步长。这些参数被称为*超参数*,因为学习的过程是识别所述预测函数的正确的参数的过程。例如,线性模型中的系数或树中的比较点。查找这些参数的过程由超参数管理。欲了解更多信息,请参阅在维基百科的超参数文章。

标签

要用机器学习模型预测的元素。例如,狗的品种或未来的股票价格。

对数损失

损失是指训练数据上的模型的准确性的算法和任务特定度量。对数损失是相同损失量的对数。

平均绝对偏差

基于平均所有模型偏差的模型评估度量标准,其中偏差是预测值距真实值的距离。

模型

传统上,这指的是预测函数的参数。例如,线性模型中的权重或树中的分割点。在 ML.NET 中,模型包含预测域对象(例如图像或文本)标签所需的所有信息。这意味着 ML.NET 模型包含必要的特征化步骤以及预测函数的参数。

多分类

一种分类情况。标签是三个或更多的类中之一。有关更多信息,请参阅Wikipedia上的多分类文章。

N元

文本数据的特征提取方案。任何N个字的序列都变成一个特征

数值特征向量

一个仅包含数值的特征向量。这与 double [] 类似。

流水线

将模型拟合到数据集所需的所有操作。流水线由数据导入、转换、特征化和学习步骤组成。一旦流水线被训练,它就变成了一个模型。

精确率

真实结果与积极结果的比例。

召回率

所有结果中的所有正确结果的所占分数比。

回归

一个有监督的机器学习任务,其输出是一个实际值,例如,double。例子包括预言和预测股票价格。

相对绝对偏差

一个评估指标,将误差表示为真实值的百分比。

相对平方偏差

通过除以预测值的总平方误差来对总平方误差进行归一化的评估度量。

均方根误差

偏差的平方的平均值,然后取该值的根,作为评估模型的评估度量之一。

监督机器学习

机器学习的一个子类,其中需要模型来预测尚未看到的数据的标签。示例包括分类,回归和结构化预测。欲了解更多信息,请参阅维基百科上的 监督学习文章。

训练

确定给定训练数据集的模型的过程。对于线性模型,这意味着找到权重。对于树来说,它涉及识别分割点。

转换

一个流水线组件,用于转换数据。例如,从文本到数字向量。

无监督机器学习

机器学习的一个子类,其中需要一个模型来发现数据中隐藏的(或潜在的)结构。示例包括聚类,主题建模和降维。欲了解更多信息,请参阅维基百科上的无监督学习文章。

一、什么是 ML.NET 以及它如何工作?

1.1 什么是ML.NET

ML.NET 使你能够在联机或脱机场景中将机器学习添加到 .NET 应用程序中。 借助此功能,可以使用应用程序的可用数据进行自动预测。 机器学习应用程序利用数据中的模式来进行预测,而不需要进行显式编程。

ML.NET 的核心是机器学习模型 。 该模型指定将输入数据转换为预测所需的步骤。 借助 ML.NET,可以通过指定算法来训练自定义模型,也可以导入预训练的 TensorFlow 和 ONNX 模型。

拥有模型后,可以将其添加到应用程序中进行预测。

ML.NET 在使用 .NET Core 的 Windows、Linux 和 macOS 或使用 .NET Framework 的 Windows 上运行。 所有平台均支持 64 位。 Windows 支持 32 位,TensorFlow、LightGBM 和 ONNX 相关功能除外。

可以使用 ML.NET 进行的预测类型的示例:

实例名称 实例描述
分类/类别划分 自动将客户反馈分为积极和消极两类
回归/预测连续值 根据面积和地段预测房价
异常检测 检测欺诈性的银行交易
建议 根据网购者以前的购买情况,推荐他们可能想购买的产品
时序/顺序数据 预测天气/产品销售额
图像分类 对医学影像中的病状进行分类
文本分类 根据文档内容对文档进行分类。
句子相似性 测量两个句子的相似程度。

1.2 Hello World案例分析

using System;
   using Microsoft.ML;
   using Microsoft.ML.Data;

   class Program
   {
       public class HouseData
       {
           public float Size { get; set; }
           public float Price { get; set; }
       }

       public class Prediction
       {
           [ColumnName("Score")]
           public float Price { get; set; }
       }

       static void Main(string[] args)
       {
           // 创建机器学习的上下文对象
           MLContext mlContext = new MLContext();

           // 1. 导入或者创建训练的数据
           HouseData[] houseData = {
               new HouseData() { Size = 1.1F, Price = 1.2F },
               new HouseData() { Size = 1.9F, Price = 2.3F },
               new HouseData() { Size = 2.8F, Price = 3.0F },
               new HouseData() { Size = 3.4F, Price = 3.7F } };
           IDataView trainingData = mlContext.Data.LoadFromEnumerable(houseData);

           // 2. 指定准备的数据和模型训练管道
           var pipeline = mlContext.Transforms.Concatenate("Features", new[] { "Size" })
               .Append(mlContext.Regression.Trainers.Sdca(labelColumnName: "Price", maximumNumberOfIterations: 100));

           // 3. 开始训练模型
           var model = pipeline.Fit(trainingData);

           // 4. 做出预测
           var size = new HouseData() { Size = 2.5F };
           var price = mlContext.Model.CreatePredictionEngine<HouseData, Prediction>(model).Predict(size);

           Console.WriteLine($"Predicted price for size: {size.Size*1000} sq ft= {price.Price*100:C}k");

           // Predicted price for size: 2500 sq ft= $261.98k
       }
   }

1.3 它如何工作的

以下关系图表示应用程序代码结构,以及模型开发的迭代过程:

ML.NET 应用程序开发流包括用于数据生成、管道开发、模型训练、模型评估和模型使用的组件

二、ML.NET快速构建应用

2.1 VS开发工具的安装

下载并安装 Visual Studio 2022。

下载 Visual Studio 2022

在安装过程中,应选择 .NET 桌面开发工作负载以及可选的 ML.NET Model Builder 组件。使用上面的链接时应预先正确选择所有先决条件,如下图所示:

Visual Studio 安装程序中的 Model Builder 组件。

已经有 Visual Studio?

如果已有 Visual Studio 2022,则可以添加 .NET 桌面开发 工作负载:

升级到最新版本的 Model Builder

在 Visual Studio中启用 ML.NET Model Builder 后,下载并安装最新版本。

2.2 构建机器学习模型

创建控制台项目

打开 Visual Studio 并新建 .NET 控制台应用:

  1. 从 Visual Studio 2022 开始窗口中选择 新建项目
  2. 选择 C# 控制台应用 项目模板。
  3. Visual Studio 开始屏幕的屏幕截图。
  4. 将项目名称更改为 myMLApp

  5. 确保不选中**将解决方案和项目置于同一目录中**。
  6. Visual Studio 项目配置屏幕的屏幕截图。
  7. 选择“**下一步**”按钮。

  8. 选择 .NET 7.0 (标准期限支持) 作为 Framework。
  9. 选择“**创建**”按钮。Visual Studio 将创建项目并加载 Program.cs 文件。

添加机器学习

  1. 右击 解决方案资源管理器 中的 myMLApp 项目,并选择 添加 > 机器学习模型

显示所选机器学习模型的 Visual Studio 的屏幕截图。

  1. 在“**添加新项目**”对话框中,确保选中“**机器学习模型(ML.NET)**”。

  2. 将“**名称**”字段更改为 SentimentModel.mbconfig,然后选择“**添加**”按钮。

“添加新项”对话框,显示了所选的机器学习模型(ML.NET)以及文件名 SentimentModel.mbconfig。

一个名为 SentimentModel.mbconfig 的新文件将添加到你的解决方案中,并且 Model Builder UI 将在 Visual Studio 的新停靠工具窗口中打开。**mbconfig** 文件只是一个 JSON 文件,用于跟踪 UI 的状态。

Model Builder 将通过以下步骤指导你完成构建机器学习模型的过程。

2.3 选取方案

若要生成模型,首先需要选择机器学习场景。Model Builder 支持多种场景:

Model Builder 支持分类、值预测、推荐、图像分类和物体检测方案。

注意:请转到“扩展”>“管理扩展”,确保没有可用于 Model Builder 的更新。本教程中使用的版本为 17.17.0。

在这种情况下,将根据客户评价的内容(文字)预测情绪。

  1. 在“Model Builder 方案”屏幕中,选择**数据分类** 方案,因为要预测注释属于哪个类别(正或负)。

Model Builder 的数据分类选项的屏幕截图。

  1. 在选择 数据分类方案后,必须选择训练环境。虽然一些方案支持在 Azure 中进行训练,但“分类”目前仅支持本地训练,因此,请保持选择 本地 环境,并继续执行 数据 步骤。

  2. 在 Model Builder 中已选择本地训练环境。

2.4 下载并添加数据

下载 UCI 机器学习存储库中的[带情绪标签的句子数据集](https://archive.ics.uci.edu/ml/machine-learning-databases/00331/sentiment labelled sentences.zip)。解压缩 sentiment labelled sentences.zip 并保存 yelp_labelled.txt 文件到 myMLApp 目录。

你的解决方案资源管理器应如下所示:

Visual Studio 解决方案资源管理器

yelp_labelled.txt 中的每一行代表用户在 Yelp 上对餐厅的不同评论。第一列代表用户留下的评论,第二列代表文本的情绪(0 为负面,1 为正面)。这些列由制表符分隔,并且数据集没有标头。数据如下所示:

yelp_labelled.txt

Wow... Loved this place.	        1
Crust is not good.	        0
Not tasty and the texture was just nasty.	        0

添加数据

在 Model Builder 中,可以从本地文件添加数据或连接到 SQL Server 数据库。这次你将从文件添加 yelp_labelled.txt

  1. 选择 文件 作为输入数据源类型。
  2. 浏览 yelp_labelled.txt。选择数据集后,数据预览会显示在 数据预览 部分中。由于数据集没有标头,因此将自动生成标头(“col0” 和 “col1”)。
  3. 在“**预测列 (标签)**”下,选择 “col1”。“**标签**”是预测内容,在本例中是在数据集的第二列 (“col1”) 中发现的情绪。
  4. 用于帮助预测标签的列称为“**特征**”。除“标签”外,数据集中的所有列都将自动选择为“特征”。在这种情况下,审阅评论列(“col0”)是特征列。可以在“**高级数据选项**”中更新特征列并修改其他数据加载选项,但在本示例中不是必需的。

Model Builder 数据步骤

2.5 训练模型

现在,将使用 yelp_labelled.txt 数据集来训练模型。

Model Builder 会根据生成性能最佳模型给定的定型时间,评估多个具有不同算法和设置的模型。

  1. 将“**训练时间**”(即希望 Model Builder 探索各种模型的时间)更改为 60 秒(如果训练后未发现模型,则可以尝试增加此数字)。请注意,对于较大的数据集,训练时间会更长。Model Builder 会根据数据集大小自动调整训练时间。
  2. You can update the optimization metric and algorithms used in Advanced training options, but it is not necessary for this example.
  3. 选择**开始训练**以开始训练过程。训练开始后,可以看到剩余时间。

训练结果

完成训练后,你可以查看训练结果摘要。

image-20231011142959879

2.6 评估模型

立即试用模型

可以在“**试用模型**”部分对样本输入进行预测。文本框中预先填充了数据集的第一行数据,但你可以更改输入并选择“**预测**”按钮来尝试不同的情绪预测。

在这种情况下,0 表示负面情绪,1 表示正面情绪。

image-20231011143736623

2.7 生成代码

训练完成后,4 个文件将作为代码隐藏自动添加到 SentimentModel.mbconfig 中:

Visual Studio 解决方案资源管理器

在 Model Builder 的 Consume 步骤中,提供了一个代码片段,用于为模型创建样本输入并使用模型对该输入进行预测。

Model Builder 还提供了**项目模板**,可以选择将其添加到解决方案中。有两个项目模板(一个控制台应用和一个 Web API)使用经过训练的模型。

image-20231011152046969

2.8 使用模型

最后一步是在最终用户应用程序中使用经过训练的模型。

  1. myMLApp 项目中的 Program.cs 代码替换为以下代码:

Program.csCopy

```csharp
using MyMLApp;
// Add input data
var sampleData = new SentimentModel.ModelInput()
{
Col0 = “This restaurant was wonderful.”
};

// Load model and predict output of sample data
var result = SentimentModel.Predict(sampleData);

// If Prediction is 1, sentiment is “Positive”; otherwise, sentiment is “Negative”
var sentiment = result.PredictedLabel == 1 ? “Positive” : “Negative”;
Console.WriteLine($“Text: {sampleData.Col0}\nSentiment: {sentiment}”);
```

  1. 运行 myMLApp (选择“**Ctrl+F5**”或“**调试**”>“**在不调试的情况下启动**”)。应看到以下输出,内容为预测输入语句是正的还是负的。

image-20231011152418361

三、矩阵因子分解和 ML.NET 生成影片推荐系统

影片推荐系统的分析:

userid,movieId,rating timestamp 我怎么才选择需要推荐给其他用户的电影ID

1、比如用户1: 1 4 7 8 用户2: 1 4 7 8 9 10 相似度

2、用户对影片的评分值组件movieID和用户矩阵(判断影片受欢迎的程度)

movieId\rating
1 5
3 3
5 2.5
8 3.5
10 4
100 5

3、想这里分析这是一片面想法而已,而需要全面思考 需要构建对应推荐算法(协同过滤,基于用户或者基于产品)

4、把数据进行转换为矩阵,不管是用户矩阵,影片矩阵。

矩阵因子分解是一种常用于推荐系统的技术,它用来处理用户-物品交互数据,通过将交互数据表示为低维度的矩阵分解结果,从而提取出潜在的用户和物品特征。具体而言,矩阵因子分解可以用于以下两个主要目的:

  1. 推荐系统:矩阵因子分解可以用于预测用户对未交互物品的喜好程度,从而实现个性化的推荐。通过将用户和物品分别表示为低维度的特征向量,可以将用户-物品交互数据表示为这些特征向量的内积。通过学习这些特征向量,可以得到一个模型,能够预测用户对未知物品的评分或偏好,从而进行个性化的推荐。
  2. 特征提取与降维:矩阵因子分解可以通过降低数据的维度来提取数据中的潜在特征。通过将原始的高维度交互数据表示为低维度的因子矩阵,可以捕捉到用户和物品之间的关联性,并发现隐藏在数据中的特征。这对于理解用户行为模式、发现物品间的相似性等任务非常有用。

总之,矩阵因子分解是一种用于处理用户-物品交互数据的方法,它可以用于推荐系统以及特征提取与降维等任务,能够提取潜在的用户和物品特征,并实现个性化推荐、发现隐藏特征等功能。

img

img

img

img

img

img

img

img

img

img

img

img

3.0 矩阵案例处理

共现矩阵

假设有5个用户,5个商品,根据是否存在购买行为(买过为1,否则0),可表示为共现矩阵 Y g t Y_{gt} Ygt

Y_gt = [1, 1, 1, 0, 0], # 用户1
       [1, 1, 1, 0, 0], # 用户2
       [1, 1, 1, 0, 0], # 用户3
       [0, 0, 0, 1, 1], # 用户4
       [0, 0, 0, 1, 1], # 用户5

其中每一行对应一个用户,每一列对应一个商品,即用户123买过商品123,用户45买过商品45。

上面的矩阵中,所有元素都是已知的,然而实际场景中,仅有少数元素是已知的,大部分位置是空缺和未知的,例如,几乎没有人买过某宝/某东商品列表中的所有商品。

因此,推荐算法的应用场景,则是对上述矩阵中存在的未知元素进行预测,例如电商场景中,预测某个用户对某个商品的购买倾向。

一个简单的例子,从上面的矩阵 Y g t Y_{gt} Ygt中“挖去”一些元素,得到如下矩阵 Y Y Y,其中问号“?”表示待估计值;

Y = [1, 1, 1, 0, ?], # 用户1
    [1, 1, 1, ?, 0], # 用户2
    [1, 1, ?, 0, 0], # 用户3
    [0, ?, 0, 1, 1], # 用户4
    [?, 0, 0, 1, 1], # 用户5

矩阵分解(MF)

“物以类聚,人以群分”,购买过相同物品的人,往往有着相同的购买倾向或兴趣。

例如,根据有“空洞”的共现矩阵 Y Y Y,用户123都买过物品12,有着相同的购买倾向,同时用户12都买过物品3,于是可以推测用户3可能也会喜欢物品3.

人群的“兴趣模式”,通常少于人的个数或物品的个数,且随着人数和物品数量的增多,这种效果会越来越明显,即人群的“兴趣模式”是稀疏的;

从矩阵的角度,也不难看出,对于上面的例子,矩阵 Y g t Y_{gt} Ygt的秩只有2,小于矩阵的行数/列数,即矩阵中存在着大量冗余信息。

从谱分析的角度,矩阵分解模型相当于一个低通滤波器:通过对共现矩阵进行低秩分解,滤掉了低能量的高频信息,保留了高能量的低频信息。

SVD实现矩阵分解(MF)

对于小型矩阵,通过SVD的方式可简单实现矩阵分解。然而,此类方式存在一些问题:

  1. 不适用于大矩阵,容易爆内存;
  2. 需要预先对缺失值进行填充;
  3. 缺失值填充后会和已知数据混淆,不能区分开。

3.1 创建控制台应用程序

开发ML.NET项目的步骤:

1、必须准备好模型训练及测试的数据

2、搭建ML.NET项目开发的环境

3、准备的数据转换为模型需要的数据结构(算法参数设置)

4、创建机器学习模型及使用处理好的数据进行对训练

5、评估模型

6、使用训练模型进行预测未知数据

7、把模型进行持久化保存。

创建项目

  1. 创建一个名为“MovieRecommender”的 C# 控制台应用程序。 单击“下一步”按钮。

  2. 选择 .NET 6 作为要使用的框架。 单击“创建” 按钮。

  3. 在项目中创建一个名为“数据”的目录来保存数据集文件 :

在“解决方案资源管理器”中,右键单击项目,然后选择“添加”>“新文件夹” 。 键入“Data”,然后按 Enter。

  1. 安装“Microsoft.ML”和“Microsoft.ML.Recommender”NuGet 包 :

    备注

除非另有说明,否则本示例使用前面提到的 NuGet 包的最新稳定版本。

在“解决方案资源管理器”中,右键单击项目,然后选择“管理 NuGet 包” 。 选择“nuget.org”作为包源,然后选择“浏览”选项卡并搜索“Microsoft.ML”,在列表中选择包,再选择“安装”按钮 。 选择“预览更改” 对话框上的“确定” 按钮,如果你同意所列包的许可条款,则选择“接受许可” 对话框上的“我接受” 按钮。 对“Microsoft.ML.Recommender”重复这些步骤 。

  1. 在 Program.cs 文件的顶部添加以下 using 语句 :

csharp
using Microsoft.ML;
   using Microsoft.ML.Trainers;
   using MovieRecommendation;

下载数据

  1. 下载两个数据集并将其保存到先前创建的“数据”文件夹中 :
  1. 在“解决方案资源管理器”中,右键单击每个 *.csv 文件,然后选择“属性”。 在“高级”下,将“复制到输出目录”的值更改为“如果较新则复制” 。

如果在 VS 中较新,则用户选择“复制”的 GIF。

加载数据

ML.NET 过程的第一步是准备并加载用于训练和测试数据的模型。

建议分级数据分为 TrainTest 数据集。 Train 数据用于适应模型。 Test 数据用于使用经过训练的模型进行预测并评估模型性能。 通常使用 TrainTest 数据进行 80/20 拆分。

以下是 *.csv 文件中数据的预览:

CVS 数据集预览的屏幕截图。

在 *.csv 文件中,有四列:

在机器学习中,用于进行预测的列称为 Features,带有返回预测的列称为 Label

想要预测影片评分,因此评分列为 Label。 其他三列,userIdmovieIdtimestamp 都用 Features 来预测 Label

特征 Label
userId rating
movieId
timestamp

由你来决定使用哪个 Features 来预测 Label。 你还可以使用排列特征重要性等方法来帮助选择最佳 Features

在此示例中,应将 timestamp 列排除为 Feature,因为时间戳并不会真正影响用户对给定影片的评分方式,因此无法进行更准确的预测:

特征 Label
userId rating
movieId

接下来,必须为输入类定义数据结构。

向项目添加一个新类:

  1. 在“解决方案资源管理器”中,右键单击该项目,然后选择“添加”>“新项”。
  2. 在“添加新项”对话框中,选择“类”并将“名称”字段更改为“MovieRatingData.cs” 。 然后,选择“添加” 按钮。

“MovieRatingData.cs”文件随即在代码编辑器中打开 。 将下面的 using 语句添加到 MovieRatingData.cs 的顶部 :

using Microsoft.ML.Data;

通过删除现有的类定义并在 MovieRatingData.cs 中添加以下代码,创建一个名为 MovieRating 的类 :

public class MovieRating
{
    [LoadColumn(0)]
    public float userId;
    [LoadColumn(1)]
    public float movieId;
    [LoadColumn(2)]
    public float Label;
}

MovieRating 指定输入数据类。 [LoadColumn](https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.ml.data.loadcolumnattribute.-ctor#microsoft-ml-data-loadcolumnattribute-ctor(system-int32)) 属性指定应加载数据集中的哪些列(按列索引)。 userIdmovieId 列是你的 Features(你将向模型提供预测 Label 的输入),而评分列是你将预测的 Label 模型的输出)。

创建另一个类 MovieRatingPrediction,通过在 MovieRatingData.cs 中的 MovieRating 类之后添加以下代码来表示预测结果:

public class MovieRatingPrediction
{
    public float Label;
    public float Score;
}

在 Program.cs 中,使用以下代码替换 Console.WriteLine("Hello World!")

MLContext mlContext = new MLContext();

执行所有 ML.NET 操作都是从 MLContext 类开始,初始化 mlContext 可创建一个新的 ML.NET 环境,可在模型创建工作流对象之间共享该环境。 从概念上讲,它与实体框架中的 DBContext 类似。

在文件底部,创建名为 LoadData() 的方法:

C#复制

(IDataView training, IDataView test) LoadData(MLContext mlContext)
{

}

除非在以下步骤中添加返回语句,否则使用此方法将出错。

初始化数据路径变量、从 *.csv 文件加载数据以及将 TrainTest 数据作为 IDataView 对象返回,方法是在 LoadData() 中添加以下代码作为下一代码行:

var trainingDataPath = Path.Combine(Environment.CurrentDirectory, "Data", "recommendation-ratings-train.csv");
var testDataPath = Path.Combine(Environment.CurrentDirectory, "Data", "recommendation-ratings-test.csv");

IDataView trainingDataView = mlContext.Data.LoadFromTextFile<MovieRating>(trainingDataPath, hasHeader: true, separatorChar: ',');
IDataView testDataView = mlContext.Data.LoadFromTextFile<MovieRating>(testDataPath, hasHeader: true, separatorChar: ',');

return (trainingDataView, testDataView);

ML.NET 中的数据表示为 IDataView 接口IDataView 是用于描述表格数据(数字和文本)的一种灵活且有效的方法。 可从文本文件或实时(例如,SQL 数据库或日志文件)将数据加载到 IDataView 对象。

[LoadFromTextFile()](https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.ml.textloadersavercatalog.loadfromtextfile#microsoft-ml-textloadersavercatalog-loadfromtextfile-1(microsoft-ml-dataoperationscatalog-system-string-system-char-system-boolean-system-boolean-system-boolean-system-boolean)) 用于定义数据架构并读取文件。 它使用数据路径变量并返回 IDataView。 在这种情况下,需提供 TestTrain 文件的路径,并指示文本文件头(以便正确使用列名称)和逗号字符数据分隔符(默认分隔符是制表符)。

添加以下代码以调用 LoadData() 方法并返回 TrainTest 数据:

(IDataView trainingDataView, IDataView testDataView) = LoadData(mlContext);

3.2 生成并训练模型

使用下面的代码紧随 LoadData() 方法后创建 BuildAndTrainModel() 方法:

ITransformer BuildAndTrainModel(MLContext mlContext, IDataView trainingDataView)
{

}

除非在以下步骤中添加返回语句,否则使用此方法将出错。

通过将以下代码添加到 BuildAndTrainModel() 来定义数据转换:

IEstimator<ITransformer> estimator = mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: "userIdEncoded", inputColumnName: "userId")
    .Append(mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: "movieIdEncoded", inputColumnName: "movieId"));

由于 userIdmovieId 代表用户和影片标题,而不是实际值,因此使用 MapValueToKey() 方法将每个 userId 和每个 movieId 转换为数字键类型 Feature 列(推荐算法接受的格式)并将它们添加为新的数据集列:

userId movieId Label userIdEncoded movieIdEncoded
1 1 4 userKey1 movieKey1
1 3 4 userKey1 movieKey2
1 6 4 userKey1 movieKey3

选择机器学习算法并将其添加到数据转换定义中,方法是在 BuildAndTrainModel() 中添加以下代码作为下一代码行:

var options = new MatrixFactorizationTrainer.Options
{
    MatrixColumnIndexColumnName = "userIdEncoded",
    MatrixRowIndexColumnName = "movieIdEncoded",
    LabelColumnName = "Label",
    NumberOfIterations = 20,
    ApproximationRank = 100
};

var trainerEstimator = estimator.Append(mlContext.Recommendation().Trainers.MatrixFactorization(options));

[MatrixFactorizationTrainer](https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.ml.recommendationcatalog.recommendationtrainers.matrixfactorization#microsoft-ml-recommendationcatalog-recommendationtrainers-matrixfactorization(microsoft-ml-trainers-matrixfactorizationtrainer-options)) 就是推荐训练算法。 当你掌握用户过去如何评价产品的数据时,通常建议使用[矩阵分解](https://en.wikipedia.org/wiki/Matrix_factorization_(recommender_systems))方法,本教程中的数据集就是这种情况。 当你有不同的数据时,还可使用其他推荐算法(请参阅下面的其他推荐算法部分以了解更多信息)。

在本例中,Matrix Factorization 算法使用了一种称为“协作筛选”的方法,该方法假设如果用户 1 在某个问题上与用户 2 有相同的观点,那么用户 1 更有可能与用户 2 在另一个问题上有相同的看法。

例如,如果用户 1 和用户 2 对影片的评分相似,那么用户 2 更有可能欣赏用户 1 已观看并给出很高评分的影片:

Incredibles 2 (2018) The Avengers (2012) Guardians of the Galaxy (2014)
用户 1 观看和点赞过的影片 观看和点赞过的影片 观看和点赞过的影片
用户 2 观看和点赞过的影片 观看和点赞过的影片 没有看过 - 推荐影片

Matrix Factorization 训练程序有多个选项,可在下面的算法超参数部分中详细了解。

BuildAndTrainModel() 方法中添加以下代码作为下一代码行,使模型适应 Train 数据,并返回经过训练的模型:

Console.WriteLine("=============== Training the model ===============");
ITransformer model = trainerEstimator.Fit(trainingDataView);

return model;

[Fit()](https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.ml.trainers.matrixfactorizationtrainer.fit#microsoft-ml-trainers-matrixfactorizationtrainer-fit(microsoft-ml-idataview-microsoft-ml-idataview)) 方法使用提供的训练数据集训练模型。 从技术上讲,该方法通过转换数据并应用训练来执行 Estimator 定义,然后返回经过训练的模型,即 Transformer

若要详细了解 ML.NET 中的模型训练工作流,请参阅什么是 ML.NET 以及它如何工作?

将以下内容添加为对 LoadData() 方法的调用下方的下一代码行,以调用 BuildAndTrainModel() 方法并返回经过训练的模型:

ITransformer model = BuildAndTrainModel(mlContext, trainingDataView);

3.3 评估模型

训练模型后,使用测试数据评估模型的执行情况。

使用下面的代码紧随 BuildAndTrainModel() 方法后创建 EvaluateModel() 方法:

void EvaluateModel(MLContext mlContext, IDataView testDataView, ITransformer model)
{

}

将以下代码添加到 EvaluateModel() 以转换 Test 数据:

Console.WriteLine("=============== Evaluating the model ===============");
var prediction = model.Transform(testDataView);

Transform() 方法对测试数据集的多个提供的输入行进行预测。

通过在 EvaluateModel() 方法中添加以下代码作为下一代码行来评估模型:

var metrics = mlContext.Regression.Evaluate(prediction, labelColumnName: "Label", scoreColumnName: "Score");

获得预测集后,Evaluate() 方法会对模型进行评估,该模型会将预测值与测试数据集中的实际 Labels 进行比较,并返回有关模型执行情况的指标。

EvaluateModel() 方法中添加以下代码作为下一代码行,将评估指标输出到控制台:

Console.WriteLine("Root Mean Squared Error : " + metrics.RootMeanSquaredError.ToString());
Console.WriteLine("RSquared: " + metrics.RSquared.ToString());

将以下内容添加为对 BuildAndTrainModel() 方法的调用下方的下一代码行,以调用 EvaluateModel() 方法:

EvaluateModel(mlContext, testDataView, model);

到目前为止的输出应类似于以下文本:

=============== Training the model ===============
iter      tr_rmse          obj
   0       1.5403   3.1262e+05
   1       0.9221   1.6030e+05
   2       0.8687   1.5046e+05
   3       0.8416   1.4584e+05
   4       0.8142   1.4209e+05
   5       0.7849   1.3907e+05
   6       0.7544   1.3594e+05
   7       0.7266   1.3361e+05
   8       0.6987   1.3110e+05
   9       0.6751   1.2948e+05
  10       0.6530   1.2766e+05
  11       0.6350   1.2644e+05
  12       0.6197   1.2541e+05
  13       0.6067   1.2470e+05
  14       0.5953   1.2382e+05
  15       0.5871   1.2342e+05
  16       0.5781   1.2279e+05
  17       0.5713   1.2240e+05
  18       0.5660   1.2230e+05
  19       0.5592   1.2179e+05
=============== Evaluating the model ===============
Rms: 0.994051469730769
RSquared: 0.412556298844873

在此输出中,有 20 次迭代。 在每次迭代中,误差测量值均会减小并逐渐趋于最小值 0。

root of mean squared error(RMS 或 RMSE)用于度量模型预测的值与测试数据集观察到的值之间的差异。 从技术上讲,它是误差的平方的平均值的平方根。 指标越低,模型就越好。

R Squared 指明数据与模型的适应程度。 范围从 0 到 1。 值 0 表示数据是随机的,否则就无法适应模型。 值 1 表示模型与数据完全匹配。 通常会希望 R Squared 分数尽可能接近 1。

生成成功的模型是一个迭代过程。 由于本教程使用小型数据集来提供快速模型训练,因此该模型的初始质量较低。 如果对模型质量不满意,可以通过尝试提供更大的训练数据集,或通过为每种算法选择具有不同超参数的不同训练算法来改进它。 有关详细信息,请查看下面的改进模型部分。

3.4 使用模型

现在,你可以使用经过训练的模型对新数据进行预测。

使用下面的代码紧随 EvaluateModel() 方法后创建 UseModelForSinglePrediction() 方法:

void UseModelForSinglePrediction(MLContext mlContext, ITransformer model)
{

}

使用 PredictionEngine 通过将以下代码添加到 UseModelForSinglePrediction() 来预测评分:

Console.WriteLine("=============== Making a prediction ===============");
var predictionEngine = mlContext.Model.CreatePredictionEngine<MovieRating, MovieRatingPrediction>(model);

PredictionEngine 是一个简便 API,可使用它对单个数据实例执行预测。 PredictionEngine 不是线程安全。 可以在单线程环境或原型环境中使用。 为了在生产环境中提高性能和线程安全,请使用 PredictionEnginePool 服务,这将创建一个在整个应用程序中使用的 PredictionEngine 对象的 ObjectPool。 请参阅本指南,了解如何在 ASP.NET Core Web API 中使用 PredictionEnginePool

备注

PredictionEnginePool 服务扩展目前处于预览状态。

创建一个名为 testInputMovieRating 实例,并通过在 UseModelForSinglePrediction() 方法中添加以下代码作为下一代码行,将其传递给预测引擎:

var testInput = new MovieRating { userId = 6, movieId = 10 };
var movieRatingPrediction = predictionEngine.Predict(testInput);

Predict() 函数对单列数据进行预测。

然后,你可以使用 Score 或预测评分来确定是否要将 movieId 10 的影片推荐给用户 6。 Score 越高,用户喜欢特定电影的可能性就越大。 在这种情况下,假设你推荐预测评分 > 3.5 的电影。

若要输出结果,请在 UseModelForSinglePrediction() 方法中添加以下代码作为下一代码行:

if (Math.Round(movieRatingPrediction.Score, 1) > 3.5)
{
    Console.WriteLine("Movie " + testInput.movieId + " is recommended for user " + testInput.userId);
}
else
{
    Console.WriteLine("Movie " + testInput.movieId + " is not recommended for user " + testInput.userId);
}

将以下内容添加为对 EvaluateModel() 方法的调用后面的下一代码行,以调用 UseModelForSinglePrediction() 方法:

UseModelForSinglePrediction(mlContext, model);

此方法的输出应类似于以下文本:

控制台

=============== Making a prediction ===============
Movie 10 is recommended for user 6

3.5 保存模型

若要使用模型在最终用户应用程序中进行预测,必须先保存模型。

使用下面的代码紧随 UseModelForSinglePrediction() 方法后创建 SaveModel() 方法:

void SaveModel(MLContext mlContext, DataViewSchema trainingDataViewSchema, ITransformer model)
{

}

通过在 SaveModel() 方法中添加以下代码来保存经过训练的模型:

var modelPath = Path.Combine(Environment.CurrentDirectory, "Data", "MovieRecommenderModel.zip");

Console.WriteLine("=============== Saving the model to a file ===============");
mlContext.Model.Save(model, trainingDataViewSchema, modelPath);

此方法会将经过训练的模型保存到 .zip 文件(在“数据”文件夹中),然后可以在其他 .NET 应用程序中使用该文件进行预测。

将以下内容添加为对 UseModelForSinglePrediction() 方法的调用后面的下一代码行,以调用 SaveModel() 方法:

SaveModel(mlContext, trainingDataView.Schema, model);

3.6 使用保存的模型

保存已定型模型后,可以在不同的环境中使用该模型。 请参阅保存和加载已定型模型,了解如何在应用中操作定型的机器学习模型。

结果

按照上述步骤操作后,运行控制台应用程序 (Ctrl + F5)。 上述单一预测的结果应与以下内容类似。 你可能会看到警告或处理消息,为清楚起见,这些消息已从以下结果中删除。

控制台复制

=============== Training the model ===============
iter      tr_rmse          obj
   0       1.5382   3.1213e+05
   1       0.9223   1.6051e+05
   2       0.8691   1.5050e+05
   3       0.8413   1.4576e+05
   4       0.8145   1.4208e+05
   5       0.7848   1.3895e+05
   6       0.7552   1.3613e+05
   7       0.7259   1.3357e+05
   8       0.6987   1.3121e+05
   9       0.6747   1.2949e+05
  10       0.6533   1.2766e+05
  11       0.6353   1.2636e+05
  12       0.6209   1.2561e+05
  13       0.6072   1.2462e+05
  14       0.5965   1.2394e+05
  15       0.5868   1.2352e+05
  16       0.5782   1.2279e+05
  17       0.5713   1.2227e+05
  18       0.5637   1.2190e+05
  19       0.5604   1.2178e+05
=============== Evaluating the model ===============
Rms: 0.977175077487166
RSquared: 0.43233349213192
=============== Making a prediction ===============
Movie 10 is recommended for user 6
=============== Saving the model to a file ===============

祝贺你! 现已成功构建了用于推荐影片的机器学习模型。 可以在 dotnet/samples 存储库中找到本教程的源代码。

3.7 提升模型

有几种方法可以提升模型的性能,以便可以获得更准确的预测。

数据

可添加更多训练数据,并在其中包括针对每个用户和影片 ID 的足够样本,以帮助提升推荐模型的质量。

交叉验证是一种评估模型的方法,它将数据随机分成子集(而不是像你在本教程中那样从数据集中提取测试数据),并将一些组作为训练数据,一些组作为测试数据。 从模型质量方面看,该方法优于进行训练-测试拆分。

特征

在本教程中,只使用数据集提供的三个 Featuresuser idmovie idrating)。

虽然这是一个良好的开端,但实际上你可能希望添加其他属性或 Features(例如,年龄、性别、地理位置等),如果它们包含在数据集中。 添加更相关的 Features 有助于提升推荐模型的性能。

如果你不确定哪个 Features 可能与机器学习任务最相关,还可以使用 ML.NET 提供的特征贡献计算 (FCC) 和排列特征重要性来发现最有影响力的 Features

算法超参数

虽然 ML.NET 提供了良好的默认训练算法,但可以通过更改算法的超参数来进一步微调性能。

对于 Matrix Factorization,可尝试使用超参数,例如 NumberOfIterationsApproximationRank 来查看是否可以获得更好的结果。

例如,在本教程中,算法选项是:

var options = new MatrixFactorizationTrainer.Options
{
    MatrixColumnIndexColumnName = "userIdEncoded",
    MatrixRowIndexColumnName = "movieIdEncoded",
    LabelColumnName = "Label",
    NumberOfIterations = 20,
    ApproximationRank = 100
};

其他推荐算法

具有协作筛选的矩阵分解算法只是用于执行影片推荐的一种方法。 在许多情况下,可能没有可用的评分数据,并且只有用户可以获得影片历史记录。 在其他情况下,你可能不仅仅拥有用户的评分数据。

算法 方案 示例
一类矩阵分解 当只有 userId 和 movieId 时使用此选项。 这种推荐方式基于共同购买方案或经常一起购买的产品,这意味着它将根据自己的采购订单历史记录向客户推荐一组产品。 >试用
场感知分解机 当拥有的特征不止 userId、productId 和评分(例如产品描述或产品价格)时,可使用此选项进行建议。 此方法也使用协作筛选法。 >试用

新用户方案

协作筛选中的一个常见问题是“冷开始问题”,即有一个新用户,没有用于进行推理的任何旧数据。 该问题通常可通过要求新用户创建个人资料来解决,例如,对他们过去看过的影片评分。 虽然此方法会给用户带来一些负担,但它可为没有评分历史记录的新用户提供一些开始数据。

四、基于API实现图像分类识别

4.1 素材准备

四类训练图片各10张

image-20231020111904434

image-20231020111958992

推断图像7张

image-20231020112032224

4.2 分析并准备数据模型类

创建图像预测类

public class ImagePrediction
{
    [ColumnName("Score")]
    public float[] Score; // 预测评分

    [ColumnName("PredictedLabel")]
    public string PredictedLabel; // 预测的类别
}

创建图像类型

public class InMemoryImageData
{
    public InMemoryImageData(byte[] image, string label, string imageFileName)
    {
        Image = image;
        Label = label;
        ImageFileName = imageFileName;
    }

    public readonly byte[] Image;

    public readonly string Label;

    public readonly string ImageFileName;
}

创建文件工具类

public class FileUtils
{
    public static IEnumerable<(string imagePath, string label)> LoadImagesFromDirectory(
        string folder,
        bool useFolderNameasLabel)
    {
        var imagesPath = Directory
            .GetFiles(folder, "*", searchOption: SearchOption.AllDirectories)
            .Where(x => Path.GetExtension(x) == ".jpg" || Path.GetExtension(x) == ".png");

        return useFolderNameasLabel
            ? imagesPath.Select(imagePath => (imagePath, Directory.GetParent(imagePath).Name))
            : imagesPath.Select(imagePath =>
                                {
                                    var label = Path.GetFileName(imagePath);
                                    for (var index = 0; index < label.Length; index++)
                                    {
                                        if (!char.IsLetter(label[index]))
                                        {
                                            label = label.Substring(0, index);
                                            break;
                                        }
                                    }
                                    return (imagePath, label);
                                });
    }

    public static IEnumerable<InMemoryImageData> LoadInMemoryImagesFromDirectory(
        string folder,
        bool useFolderNameAsLabel = true)
        => LoadImagesFromDirectory(folder, useFolderNameAsLabel)
        .Select(x => new InMemoryImageData(
            image: File.ReadAllBytes(x.imagePath),
            label: x.label,
            imageFileName: Path.GetFileName(x.imagePath)));

    public static string GetAbsolutePath(Assembly assembly, string relativePath)
    {
        var assemblyFolderPath = new FileInfo(assembly.Location).Directory.FullName;

        return Path.Combine(assemblyFolderPath, relativePath);
    }
}

4.3 模型训练

拷贝素材到指定位置

编写模型的核心训练逻辑代码

编写数据模型类

public class ImageData
{
    public ImageData(string imagePath, string label)
    {
        ImagePath = imagePath;
        Label = label;
    }

    public readonly string ImagePath;

    public readonly string Label;
}

封装控制台输出类

public static class ConsoleHelper
{
    public static void PrintPrediction(string prediction)
    {
        Console.WriteLine($"*************************************************");
        Console.WriteLine($"Predicted : {prediction}");
        Console.WriteLine($"*************************************************");
    }

    public static void PrintRegressionPredictionVersusObserved(string predictionCount, string observedCount)
    {
        Console.WriteLine($"-------------------------------------------------");
        Console.WriteLine($"Predicted : {predictionCount}");
        Console.WriteLine($"Actual:     {observedCount}");
        Console.WriteLine($"-------------------------------------------------");
    }

    public static void PrintRegressionMetrics(string name, RegressionMetrics metrics)
    {
        Console.WriteLine($"*************************************************");
        Console.WriteLine($"*       Metrics for {name} regression model      ");
        Console.WriteLine($"*------------------------------------------------");
        Console.WriteLine($"*       LossFn:        {metrics.LossFunction:0.##}");
        Console.WriteLine($"*       R2 Score:      {metrics.RSquared:0.##}");
        Console.WriteLine($"*       Absolute loss: {metrics.MeanAbsoluteError:#.##}");
        Console.WriteLine($"*       Squared loss:  {metrics.MeanSquaredError:#.##}");
        Console.WriteLine($"*       RMS loss:      {metrics.RootMeanSquaredError:#.##}");
        Console.WriteLine($"*************************************************");
    }

    public static void PrintBinaryClassificationMetrics(string name, CalibratedBinaryClassificationMetrics metrics)
    {
        Console.WriteLine($"************************************************************");
        Console.WriteLine($"*       Metrics for {name} binary classification model      ");
        Console.WriteLine($"*-----------------------------------------------------------");
        Console.WriteLine($"*       Accuracy: {metrics.Accuracy:P2}");
        Console.WriteLine($"*       Area Under Curve:      {metrics.AreaUnderRocCurve:P2}");
        Console.WriteLine($"*       Area under Precision recall Curve:  {metrics.AreaUnderPrecisionRecallCurve:P2}");
        Console.WriteLine($"*       F1Score:  {metrics.F1Score:P2}");
        Console.WriteLine($"*       LogLoss:  {metrics.LogLoss:#.##}");
        Console.WriteLine($"*       LogLossReduction:  {metrics.LogLossReduction:#.##}");
        Console.WriteLine($"*       PositivePrecision:  {metrics.PositivePrecision:#.##}");
        Console.WriteLine($"*       PositiveRecall:  {metrics.PositiveRecall:#.##}");
        Console.WriteLine($"*       NegativePrecision:  {metrics.NegativePrecision:#.##}");
        Console.WriteLine($"*       NegativeRecall:  {metrics.NegativeRecall:P2}");
        Console.WriteLine($"************************************************************");
    }

    public static void PrintAnomalyDetectionMetrics(string name, AnomalyDetectionMetrics metrics)
    {
        Console.WriteLine($"************************************************************");
        Console.WriteLine($"*       Metrics for {name} anomaly detection model      ");
        Console.WriteLine($"*-----------------------------------------------------------");
        Console.WriteLine($"*       Area Under ROC Curve:                       {metrics.AreaUnderRocCurve:P2}");
        Console.WriteLine($"*       Detection rate at false positive count: {metrics.DetectionRateAtFalsePositiveCount}");
        Console.WriteLine($"************************************************************");
    }

    public static void PrintMultiClassClassificationMetrics(string name, MulticlassClassificationMetrics metrics)
    {
        Console.WriteLine($"************************************************************");
        Console.WriteLine($"*    Metrics for {name} multi-class classification model   ");
        Console.WriteLine($"*-----------------------------------------------------------");
        Console.WriteLine($"    AccuracyMacro = {metrics.MacroAccuracy:0.####}, a value between 0 and 1, the closer to 1, the better");
        Console.WriteLine($"    AccuracyMicro = {metrics.MicroAccuracy:0.####}, a value between 0 and 1, the closer to 1, the better");
        Console.WriteLine($"    LogLoss = {metrics.LogLoss:0.####}, the closer to 0, the better");

        int i = 0;
        foreach (var classLogLoss in metrics.PerClassLogLoss)
        {
            i++;
            Console.WriteLine($"    LogLoss for class {i} = {classLogLoss:0.####}, the closer to 0, the better");
        }
        Console.WriteLine($"************************************************************");
    }

    public static void PrintRegressionFoldsAverageMetrics(string algorithmName, IReadOnlyList<CrossValidationResult<RegressionMetrics>> crossValidationResults)
    {
        var L1 = crossValidationResults.Select(r => r.Metrics.MeanAbsoluteError);
        var L2 = crossValidationResults.Select(r => r.Metrics.MeanSquaredError);
        var RMS = crossValidationResults.Select(r => r.Metrics.RootMeanSquaredError);
        var lossFunction = crossValidationResults.Select(r => r.Metrics.LossFunction);
        var R2 = crossValidationResults.Select(r => r.Metrics.RSquared);

        Console.WriteLine($"*************************************************************************************************************");
        Console.WriteLine($"*       Metrics for {algorithmName} Regression model      ");
        Console.WriteLine($"*------------------------------------------------------------------------------------------------------------");
        Console.WriteLine($"*       Average L1 Loss:    {L1.Average():0.###} ");
        Console.WriteLine($"*       Average L2 Loss:    {L2.Average():0.###}  ");
        Console.WriteLine($"*       Average RMS:          {RMS.Average():0.###}  ");
        Console.WriteLine($"*       Average Loss Function: {lossFunction.Average():0.###}  ");
        Console.WriteLine($"*       Average R-squared: {R2.Average():0.###}  ");
        Console.WriteLine($"*************************************************************************************************************");
    }

    public static void PrintMulticlassClassificationFoldsAverageMetrics(
        string algorithmName,
        IReadOnlyList<CrossValidationResult<MulticlassClassificationMetrics>> crossValResults
    )
    {
        var metricsInMultipleFolds = crossValResults.Select(r => r.Metrics);

        var microAccuracyValues = metricsInMultipleFolds.Select(m => m.MicroAccuracy);
        var microAccuracyAverage = microAccuracyValues.Average();
        var microAccuraciesStdDeviation = CalculateStandardDeviation(microAccuracyValues);
        var microAccuraciesConfidenceInterval95 = CalculateConfidenceInterval95(microAccuracyValues);

        var macroAccuracyValues = metricsInMultipleFolds.Select(m => m.MacroAccuracy);
        var macroAccuracyAverage = macroAccuracyValues.Average();
        var macroAccuraciesStdDeviation = CalculateStandardDeviation(macroAccuracyValues);
        var macroAccuraciesConfidenceInterval95 = CalculateConfidenceInterval95(macroAccuracyValues);

        var logLossValues = metricsInMultipleFolds.Select(m => m.LogLoss);
        var logLossAverage = logLossValues.Average();
        var logLossStdDeviation = CalculateStandardDeviation(logLossValues);
        var logLossConfidenceInterval95 = CalculateConfidenceInterval95(logLossValues);

        var logLossReductionValues = metricsInMultipleFolds.Select(m => m.LogLossReduction);
        var logLossReductionAverage = logLossReductionValues.Average();
        var logLossReductionStdDeviation = CalculateStandardDeviation(logLossReductionValues);
        var logLossReductionConfidenceInterval95 = CalculateConfidenceInterval95(logLossReductionValues);

        Console.WriteLine($"*************************************************************************************************************");
        Console.WriteLine($"*       Metrics for {algorithmName} Multi-class Classification model      ");
        Console.WriteLine($"*------------------------------------------------------------------------------------------------------------");
        Console.WriteLine($"*       Average MicroAccuracy:    {microAccuracyAverage:0.###}  - Standard deviation: ({microAccuraciesStdDeviation:#.###})  - Confidence Interval 95%: ({microAccuraciesConfidenceInterval95:#.###})");
        Console.WriteLine($"*       Average MacroAccuracy:    {macroAccuracyAverage:0.###}  - Standard deviation: ({macroAccuraciesStdDeviation:#.###})  - Confidence Interval 95%: ({macroAccuraciesConfidenceInterval95:#.###})");
        Console.WriteLine($"*       Average LogLoss:          {logLossAverage:#.###}  - Standard deviation: ({logLossStdDeviation:#.###})  - Confidence Interval 95%: ({logLossConfidenceInterval95:#.###})");
        Console.WriteLine($"*       Average LogLossReduction: {logLossReductionAverage:#.###}  - Standard deviation: ({logLossReductionStdDeviation:#.###})  - Confidence Interval 95%: ({logLossReductionConfidenceInterval95:#.###})");
        Console.WriteLine($"*************************************************************************************************************");

    }

    public static double CalculateStandardDeviation (IEnumerable<double> values)
    {
        double average = values.Average();
        double sumOfSquaresOfDifferences = values.Select(val => (val - average) * (val - average)).Sum();
        double standardDeviation = Math.Sqrt(sumOfSquaresOfDifferences / (values.Count()-1));
        return standardDeviation;
    }

    public static double CalculateConfidenceInterval95(IEnumerable<double> values)
    {
        double confidenceInterval95 = 1.96 * CalculateStandardDeviation(values) / Math.Sqrt((values.Count()-1));
        return confidenceInterval95;
    }

    public static void PrintClusteringMetrics(string name, ClusteringMetrics metrics)
    {
        Console.WriteLine($"*************************************************");
        Console.WriteLine($"*       Metrics for {name} clustering model      ");
        Console.WriteLine($"*------------------------------------------------");
        Console.WriteLine($"*       Average Distance: {metrics.AverageDistance}");
        Console.WriteLine($"*       Davies Bouldin Index is: {metrics.DaviesBouldinIndex}");
        Console.WriteLine($"*************************************************");
    }

    public static void ShowDataViewInConsole(MLContext mlContext, IDataView dataView, int numberOfRows = 4)
    {
        string msg = string.Format("Show data in DataView: Showing {0} rows with the columns", numberOfRows.ToString());
        ConsoleWriteHeader(msg);

        var preViewTransformedData = dataView.Preview(maxRows: numberOfRows);

        foreach (var row in preViewTransformedData.RowView)
        {
            var ColumnCollection = row.Values;
            string lineToPrint = "Row--> ";
            foreach (KeyValuePair<string, object> column in ColumnCollection)
            {
                lineToPrint += $"| {column.Key}:{column.Value}";
            }
            Console.WriteLine(lineToPrint + "\n");
        }
    }

    [Conditional("DEBUG")]
    // This method using 'DebuggerExtensions.Preview()' should only be used when debugging/developing, not for release/production trainings
    public static void PeekDataViewInConsole(MLContext mlContext, IDataView dataView, IEstimator<ITransformer> pipeline, int numberOfRows = 4)
    {
        string msg = string.Format("Peek data in DataView: Showing {0} rows with the columns", numberOfRows.ToString());
        ConsoleWriteHeader(msg);

        //https://github.com/dotnet/machinelearning/blob/main/docs/code/MlNetCookBook.md#how-do-i-look-at-the-intermediate-data
        var transformer = pipeline.Fit(dataView);
        var transformedData = transformer.Transform(dataView);

        // 'transformedData' is a 'promise' of data, lazy-loading. call Preview
        //and iterate through the returned collection from preview.

        var preViewTransformedData = transformedData.Preview(maxRows: numberOfRows);

        foreach (var row in preViewTransformedData.RowView)
        {
            var ColumnCollection = row.Values;
            string lineToPrint = "Row--> ";
            foreach (KeyValuePair<string, object> column in ColumnCollection)
            {
                lineToPrint += $"| {column.Key}:{column.Value}";
            }
            Console.WriteLine(lineToPrint + "\n");
        }
    }

    [Conditional("DEBUG")]
    // This method using 'DebuggerExtensions.Preview()' should only be used when debugging/developing, not for release/production trainings
    public static void PeekVectorColumnDataInConsole(MLContext mlContext, string columnName, IDataView dataView, IEstimator<ITransformer> pipeline, int numberOfRows = 4)
    {
        string msg = string.Format("Peek data in DataView: : Show {0} rows with just the '{1}' column", numberOfRows, columnName );
        ConsoleWriteHeader(msg);

        var transformer = pipeline.Fit(dataView);
        var transformedData = transformer.Transform(dataView);

        // Extract the 'Features' column.
        var someColumnData = transformedData.GetColumn<float[]>(columnName)
            .Take(numberOfRows).ToList();

        // print to console the peeked rows

        int currentRow = 0;
        someColumnData.ForEach(row => {
            currentRow++;
            String concatColumn = String.Empty;
            foreach (float f in row)
            {
                concatColumn += f.ToString();
            }

            Console.WriteLine();
            string rowMsg = string.Format("**** Row {0} with '{1}' field value ****", currentRow, columnName);
            Console.WriteLine(rowMsg);
            Console.WriteLine(concatColumn);
            Console.WriteLine();
        });
    }

    public static void ConsoleWriteHeader(params string[] lines)
    {
        var defaultColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.WriteLine(" ");
        foreach (var line in lines)
        {
            Console.WriteLine(line);
        }
        var maxLength = lines.Select(x => x.Length).Max();
        Console.WriteLine(new string('#', maxLength));
        Console.ForegroundColor = defaultColor;
    }

    public static void ConsoleWriterSection(params string[] lines)
    {
        var defaultColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(" ");
        foreach (var line in lines)
        {
            Console.WriteLine(line);
        }
        var maxLength = lines.Select(x => x.Length).Max();
        Console.WriteLine(new string('-', maxLength));
        Console.ForegroundColor = defaultColor;
    }

    public static void ConsolePressAnyKey()
    {
        var defaultColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine(" ");
        Console.WriteLine("Press any key to finish.");
        Console.ReadKey();
    }

    public static void ConsoleWriteException(params string[] lines)
    {
        var defaultColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Red;
        const string exceptionTitle = "EXCEPTION";
        Console.WriteLine(" ");
        Console.WriteLine(exceptionTitle);
        Console.WriteLine(new string('#', exceptionTitle.Length));
        Console.ForegroundColor = defaultColor;
        foreach (var line in lines)
        {
            Console.WriteLine(line);
        }
    }

    public static void ConsoleWriteWarning(params string[] lines)
    {
        var defaultColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.DarkMagenta;
        const string warningTitle = "WARNING";
        Console.WriteLine(" ");
        Console.WriteLine(warningTitle);
        Console.WriteLine(new string('#', warningTitle.Length));
        Console.ForegroundColor = defaultColor;
        foreach (var line in lines)
        {
            Console.WriteLine(line);
        }
    }
}

编写训练逻辑

const string assetsRelativePath = @"../../../assets";
string assetsPath = GetAbsolutePath(assetsRelativePath);

string outputMlNetModelFilePath = Path.Combine(assetsPath, "outputs", "imageClassifier.zip");
string imagesFolderPathForPredictions = Path.Combine(assetsPath, "inputs", "test-images");

string imagesDownloadFolderPath = Path.Combine(assetsPath, "inputs", "images");

// 1. Download the image set and unzip
string fullImagesetFolderPath = Path.Combine(imagesDownloadFolderPath, finalImagesFolderName);

var mlContext = new MLContext(seed: 1);

// Specify MLContext Filter to only show feedback log/traces about ImageClassification
// This is not needed for feedback output if using the explicit MetricsCallback parameter
mlContext.Log += FilterMLContextLog;           

// 2. Load the initial full image-set into an IDataView and shuffle so it'll be better balanced
IEnumerable<ImageData> images = LoadImagesFromDirectory(folder: fullImagesetFolderPath, useFolderNameAsLabel: true);
IDataView fullImagesDataset = mlContext.Data.LoadFromEnumerable(images);
IDataView shuffledFullImageFilePathsDataset = mlContext.Data.ShuffleRows(fullImagesDataset);

// 3. Load Images with in-memory type within the IDataView and Transform Labels to Keys (Categorical)
IDataView shuffledFullImagesDataset = mlContext.Transforms.Conversion.
    MapValueToKey(outputColumnName: "LabelAsKey", inputColumnName: "Label", keyOrdinality: KeyOrdinality.ByValue)
    .Append(mlContext.Transforms.LoadRawImageBytes(
        outputColumnName: "Image",
        imageFolder: fullImagesetFolderPath,
        inputColumnName: "ImagePath"))
    .Fit(shuffledFullImageFilePathsDataset)
    .Transform(shuffledFullImageFilePathsDataset);

// 4. Split the data 80:20 into train and test sets, train and evaluate.
var trainTestData = mlContext.Data.TrainTestSplit(shuffledFullImagesDataset, testFraction: 0.2);
IDataView trainDataView = trainTestData.TrainSet;
IDataView testDataView = trainTestData.TestSet;

// 5. Define the model's training pipeline using DNN default values
//
var pipeline = mlContext.MulticlassClassification.Trainers
    .ImageClassification(featureColumnName: "Image",
                         labelColumnName: "LabelAsKey",
                         validationSet: testDataView)
    .Append(mlContext.Transforms.Conversion.MapKeyToValue(outputColumnName: "PredictedLabel",
                                                          inputColumnName: "PredictedLabel"));

// 5.1 (OPTIONAL) Define the model's training pipeline by using explicit hyper-parameters
//
//var options = new ImageClassificationTrainer.Options()
//{
//    FeatureColumnName = "Image",
//    LabelColumnName = "LabelAsKey",
//    // Just by changing/selecting InceptionV3/MobilenetV2/ResnetV250  
//    // you can try a different DNN architecture (TensorFlow pre-trained model). 
//    Arch = ImageClassificationTrainer.Architecture.MobilenetV2,
//    Epoch = 50,       //100
//    BatchSize = 10,
//    LearningRate = 0.01f,
//    MetricsCallback = (metrics) => Console.WriteLine(metrics),
//    ValidationSet = testDataView
//};

//var pipeline = mlContext.MulticlassClassification.Trainers.ImageClassification(options)
//        .Append(mlContext.Transforms.Conversion.MapKeyToValue(
//            outputColumnName: "PredictedLabel",
//            inputColumnName: "PredictedLabel"));

// 6. Train/create the ML model
Console.WriteLine("*** Training the image classification model with DNN Transfer Learning on top of the selected pre-trained model/architecture ***");

// Measuring training time
var watch = Stopwatch.StartNew();

//Train
ITransformer trainedModel = pipeline.Fit(trainDataView);

watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;

Console.WriteLine($"Training with transfer learning took: {elapsedMs / 1000} seconds");

4.4 模型评估

// 7. Get the quality metrics (accuracy, etc.)
EvaluateModel(mlContext, testDataView, trainedModel);

// 使用训练好的模型进行评估
private static void EvaluateModel(MLContext mlContext, IDataView testDataset, ITransformer trainedModel)
{
    Console.WriteLine("Making predictions in bulk for evaluating model's quality...");

    // Measuring time
    var watch = Stopwatch.StartNew();

    var predictionsDataView = trainedModel.Transform(testDataset);

    var metrics = mlContext.MulticlassClassification.Evaluate(predictionsDataView, labelColumnName:"LabelAsKey", predictedLabelColumnName: "PredictedLabel");
    ConsoleHelper.PrintMultiClassClassificationMetrics("TensorFlow DNN Transfer Learning", metrics);

    watch.Stop();
    var elapsed2Ms = watch.ElapsedMilliseconds;

    Console.WriteLine($"Predicting and Evaluation took: {elapsed2Ms / 1000} seconds");
}

4.5 模型推断

// 9. Try a single prediction simulating an end-user app
TrySinglePrediction(imagesFolderPathForPredictions, mlContext, trainedModel);

public static IEnumerable<ImageData> LoadImagesFromDirectory(
    string folder,
    bool useFolderNameAsLabel = true)
    => FileUtils.LoadImagesFromDirectory(folder, useFolderNameAsLabel)
    .Select(x => new ImageData(x.imagePath, x.label));

private static void TrySinglePrediction(string imagesFolderPathForPredictions, MLContext mlContext, ITransformer trainedModel)
{
    // Create prediction function to try one prediction
    var predictionEngine = mlContext.Model
        .CreatePredictionEngine<InMemoryImageData, ImagePrediction>(trainedModel);

    var testImages = FileUtils.LoadInMemoryImagesFromDirectory(
        imagesFolderPathForPredictions, false);

    var imageToPredict = testImages.First();

    var prediction = predictionEngine.Predict(imageToPredict);

    Console.WriteLine(
        $"Image Filename : [{imageToPredict.ImageFileName}], " +
        $"Scores : [{string.Join(",", prediction.Score)}], " +
        $"Predicted Label : {prediction.PredictedLabel}");
}

4.6 提供推断api